package cn.saltice.idempotence.idem.aspect;

import cn.saltice.idempotence.common.ResposeData;
import cn.saltice.idempotence.idem.common.ContextHolder;
import cn.saltice.idempotence.idem.exception.IdempotentException;
import cn.saltice.idempotence.idem.idempotent.DistributedIdempotent;
import cn.saltice.idempotence.idem.request.IdempotentRequest;
import cn.saltice.idempotence.idem.utils.ReflectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.descriptor.web.ContextHandler;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * @program: idempotence
 * @description:
 * @author: Mr.Liu
 * @create: 2020-10-07 21:59
 **/
@Aspect
@Component
@Slf4j
public class DistributedIdempotentAspect extends AbstractIdempotentAspectSupport{
    private DistributedIdempotent distributedIdempotent;

    public DistributedIdempotentAspect(DistributedIdempotent distributedIdempotent) {
        this.distributedIdempotent = distributedIdempotent;
    }

    @Around(value = "@annotation(idempotent)")
    public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable{
        Object[] args = joinPoint.getArgs();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        String key = "";
        if (StringUtils.hasText(idempotent.spelKey())){
            // 这个key应该用一个分布式id生成器来生成，不能由用户自己提供
            key = parseKey(idempotent.spelKey(), method, args);
        }else {
            key = ContextHolder.getRequestContext().get("globalIdempotentId");
        }

        String userInputKey = idempotent.lockName();
        if (!StringUtils.hasText(userInputKey)){
            userInputKey = method.getName();
        }
        String idempotentKey = userInputKey + ":" + key;
        IdempotentRequest request = IdempotentRequest.builder().key(idempotentKey)
                .firstLevelExpireTime(idempotent.firstLevelExpireTime())
                .secondLevelExpireTime(idempotent.secondLevelExpireTime())
                .timeUnit(idempotent.timeUnit())
                .lockExpireTime(idempotent.lockExpireTime())
                .readWriteType(idempotent.readWriteType())
                .build();
        if (key == null){
            return tis(joinPoint, idempotent, method, request,new IdempotentException("未获取到key"));
        }
        try {
            return distributedIdempotent.execute(request,(k,ex)->{
                try {
                    return joinPoint.proceed();
                }catch (Throwable e){
                    log.error("幂等执行异常");
                    //throw new IdempotentException(e);
                    return tis(joinPoint, idempotent, method, request,new IdempotentException(e));
                }
            },(k,ex)->{
                //throw new IdempotentException("重复请求");
                log.error("重复请求,执行幂等处理");
                return tis(joinPoint, idempotent, method, request,ex);
            });
        }catch (IdempotentException ex){
            return handleIdempotentException(joinPoint, idempotent, ex);
        }
    }

    /**
     *  执行幂等处理函数
     * @param joinPoint 切点
     * @param idempotent 主解
     * @param method 方法
     * @param request 请求
     * @return
     */
    private Object tis(ProceedingJoinPoint joinPoint, Idempotent idempotent, Method method, IdempotentRequest request, IdempotentException ex){
        /**获取当前被切的那个类**/
        Class targetClass = ReflectionUtils.getDeclaringType(joinPoint.getTarget().getClass(), method.getName(), method.getParameterTypes());
        // 获取该类（不包括父类）的public，private, protected, default (package)方法
        Method[] methods = targetClass.getDeclaredMethods();
        for (Method m : methods){
            if (m.getName().equals(idempotent.idempotentHandler())){
                // 执行幂等处理
                Class<?>[] classes = m.getParameterTypes();
                // 参数类型限制为两种：IdempotentRequest request, IdempotentException e
                List s = Arrays.asList(classes);
                if (s.size() == 2 && s.contains(IdempotentRequest.class) && s.contains(IdempotentException.class)){
                    log.debug("执行幂等处理函数");
                    try {
                        return m.invoke(joinPoint.getTarget(),request,ex);
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        throw new IdempotentException(e);
                    }
                }
            }
        }
        log.error("未获取到key，并且处理函数格式错误[{},{}]",IdempotentRequest.class, IdempotentException.class);
        throw new IdempotentException("未获取到key，并且处理函数格式错误");
    }
    /**
     * 获取幂等的key, 支持SPEL表达式
     * @param key spelKey
     * @param method 方法参数
     * @param args 方法参数值
     * @return 解析匹配到的key
     */
    private String parseKey(String key, Method method, Object[] args){
        LocalVariableTableParameterNameDiscoverer nameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
        // 获取方法的参数名字
        String[] paraNameArr = nameDiscoverer.getParameterNames(method);

        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        if (paraNameArr != null && paraNameArr.length > 0){
            for(int i = 0;i < paraNameArr.length; i++){
                context.setVariable(paraNameArr[i], args[i]);
            }
        }
        try {
            return parser.parseExpression(key).getValue(context, String.class);
        } catch (SpelEvaluationException e) {
            throw new RuntimeException("SPEL表达式解析错误", e);
        }
    }
}
